Mô tả kỹ thuật Lỗi tràn bộ nhớ đệm

Một lỗi tràn bộ nhớ đệm xảy ra khi dữ liệu được viết vào một bộ nhớ đệm, mà do không kiểm tra biên đầy đủ nên đã ghi đè lên vùng bộ nhớ liền kề và làm hỏng các giá trị dữ liệu tại các địa chỉ bộ nhớ kề với vùng bộ nhớ đệm đó. Hiện tượng này hay xảy ra nhất khi sao chép một xâu ký tự từ một bộ nhớ đệm này sang một vùng bộ nhớ đệm khác.

Ví dụ cơ bản

Trong ví dụ sau, một chương trình đã định nghĩa hai phần tử dữ liệu kề nhau trong bộ nhớ: A là một bộ nhớ đệm xâu ký tự dài 8 byte, và B là một số nguyên kích thước 2 byte. Ban đầu, A chỉ chứa toàn các byte giá trị 0, còn B chứa giá trị 3. Các ký tự có kích thước 1 byte.

Tên biến A A A A A A A A B B
Giá trị 0 0 0 0 0 0 0 0 0 3

Bây giờ, chương trình ghi một xâu ký tự "excessive" vào bộ đệm A, theo sau là một byte 0 để đánh dấu kết thúc xâu. Vì không kiểm tra độ dài xâu, nên xâu ký tự mới đã đè lên giá trị của B:

Tên biến A A A A A A A A B B
Giá trị 'e' 'x' 'c' 'e' 's' 's' 'i' 'v' 'e' 0

Tuy lập trình viên không có ý định sửa đổi B, nhưng giá trị của B đã bị thay thế bởi một số được tạo nên từ phần cuối của xâu ký tự. Trong ví dụ này, trên một hệ thống big-endian sử dụng mã ASCII, ký tự "e" và tiếp theo là một byte 0 sẽ trở thành số 25856.

Nếu B là phần tử dữ liệu duy nhất còn lại trong số các biến được chương trình định nghĩa, việc viết một xâu ký tự dài hơn nữa và vượt quá phần cuối của B sẽ có thể gây ra một lỗi chẳng hạn như segmentation fault (lỗi phân đoạn) và tiến trình sẽ kết thúc.

Tràn bộ nhớ đệm trên stack

Bên cạnh việc sửa đổi các biến không liên quan, hiện tượng tràn bộ đệm còn thường bị lợi dụng (khai thác) bởi tin tặc để làm cho một chương trình đang chạy thực thi một đoạn mã tùy ý được cung cấp. Các kỹ thuật để một tin tặc chiếm quyền điều khiển một tiến trình tùy theo vùng bộ nhớ mà bộ đệm được đặt tại đó. Ví dụ, vùng bộ nhớ stack, nơi dữ liệu có thể được tạm thời "đẩy" xuống "đỉnh" ngăn xếp (push), và sau đó được "nhấc ra" (pop) để đọc giá trị của biến. Thông thường, khi một hàm (function) bắt đầu thực thi, các phần tử dữ liệu tạm thời (các biến địa phương) được đẩy vào, và chương trình có thể truy nhập đến các dữ liệu này trong suốt thời gian chạy hàm đó. Không chỉ có hiện tượng tràn stack (stack overflow) mà còn có cả tràn heap (heap overflow).

Trong ví dụ sau, "X" là dữ liệu đã từng nằm tại stack khi chương trình bắt đầu thực thi; sau đó chương trình gọi hàm "Y", hàm này đòi hỏi một lượng nhỏ bộ nhớ cho riêng mình; và sau đó "Y" gọi hàm "Z", "Z" đòi hỏi một bộ nhớ đệm lớn:

Z Z Z Z Z Z Y X X X
            : / / /

Nếu hàm "Z" gây tràn bộ nhớ đệm, nó có thể ghi đè dữ liệu thuộc về hàm Y hay chương trình chính:

Z Z Z Z Z Z Y X X X
. . . . . . . . / /

Điều này đặc biệt nghiêm trọng đối với hầu hết các hệ thống. Ngoài các dữ liệu thường, bộ nhớ stack còn lưu giữ địa chỉ trả về, nghĩa là vị trí của phần chương trình đang chạy trước khi hàm hiện tại được gọi. Khi hàm kết thúc, vùng bộ nhớ tạm thời sẽ được lấy ra khỏi stack, và thực thi được trao lại cho địa chỉ trả về. Như vậy, nếu địa chỉ trả về đã bị ghi đè bởi một lỗi tràn bộ đệm, nó sẽ trỏ tới một vị trí nào đó khác. Trong trường hợp một hiện tượng tràn bộ đệm không có chủ ý như trong ví dụ đầu tiên, hầu như chắc chắn rằng vị trí đó sẽ là một vị trí không hợp lệ, không chứa một lệnh nào của chương trình, và tiến trình sẽ đổ vỡ. Tuy nhiên, một kẻ tấn công có thể chỉnh địa chỉ trả về để trỏ tới một vị trí tùy ý sao cho nó có thể làm tổn hại an ninh hệ thống.

Mã nguồn ví dụ

Mã nguồn C dưới đây thể hiện một lỗi lập trình thường gặp. Sau khi được biên dịch, chương trình sẽ tạo ra một lỗi tràn bộ đệm nếu nó được gọi với một tham số dòng lệnh là một xâu ký tự quá dài, vì tham số này được dùng để ghi vào một bộ nhớ đệm mà không kiểm tra độ dài của nó.[1]

/* overflow.c - demonstrates a buffer overflow */#include <stdio.h>#include <string.h>int main(int argc, char *argv[]){ char buffer[10]; if (argc < 2) { fprintf(stderr, "USAGE: %s string\n", argv[0]); return 1; } strcpy(buffer, argv[1]); return 0;}

Các xâu ký tự độ dài không quá 9 sẽ không gây tràn bộ đệm. Các xâu ký tự gồm từ 10 ký tự trở lên sẽ gây tràn bộ đệm: hiện tượng này luôn luôn là một lỗi sai nhưng không phải lúc nào cũng gây ra việc chương trình chạy sai hay gây lỗi segmentation fault.

Chương trình trên có thể được viết lại cho an toàn bằng cách sử dụng hàm strncpy như sau:[1]

#include <string.h>#include <stdio.h>int main(int argc, char *argv[]){ char buffer[10]; if (argc < 2) { fprintf(stderr, "USAGE: %s string\n", argv[0]); return 1; } strncpy(buffer, argv[1], sizeof(buffer)); buffer[sizeof(buffer) - 1] = '\0'; return 0;}

Tài liệu tham khảo

WikiPedia: Lỗi tràn bộ nhớ đệm http://www.buffershield.com/english/products/produ... http://www.eeye.com/html/Research/Advisories/AL200... http://metasploit.com/users/opcode/msfopcode.cgi http://technet2.microsoft.com/WindowsServer/en/Lib... http://www.microsoft.com/technet/security/bulletin... http://www.microsoft.com/technet/security/bulletin... http://www.security-express.com/archives/bugtraq/1... http://world.std.com/~franl/worm.html http://www.syngress.com/book_catalog/327_SSPC/samp... http://arri.berlios.de/